前面透過 Geth 建立 POA 節點
步驟看起來似呼是有點複雜
筆者覺得 ganache 是一個比較簡單好使用的 local 測試節點
今天將透過 Truffle 建構 pet-shop Dapp 並且發佈在 ganache 上
並且使用 MetaMask 錢包作為簽章工具
yarn global add ganache-cli
透過 ganache-cli 可以開啟一個 rpc 為 http://localhost:8545 且 chainId 為 1337 的節點
並且預設建立 10個 account 並且每個 account 會給予 100 ETH
如下
ganache-cli
設定網路
把其剛剛的帳號的密鑰匯入 MetaMask
mkdir pet-shop-dapp
cd pet-shop-dapp
truffle unbox pet-shop
更新 truffle-config.js 如下
module.exports = {
// See <http://truffleframework.com/docs/advanced/configuration>
// for more about customizing your Truffle configuration!
networks: {
development: {
host: "127.0.0.1",
port: 8545,
network_id: "*" // Match any network id
},
develop: {
port: 8545
}
},
compilers: {
solc: {
version: "0.8.0"
}
},
mocha: {
// timeout: 100000
},
};
建立 contracts/Adoption.sol 如下
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Adoption {
address[16] public adopters;
// adopting a pet
function adopt(uint petId) public returns (uint) {
require(petId >= 0 && petId <= 15);
adopters[petId] = msg.sender;
return petId;
}
// retrieving the adopters
function getAdopters() public view returns (address[16] memory) {
return adopters;
}
}
adopters 是一個用來紀錄領養人的結構
主要有兩個 functions
adopt 負責把對應的領養人紀錄到 array 對應的位置
getAdopters 取得整個領養人資料
Truffle 測試 contract 可以利用 solidity 語法或是 javascript 語法
因為主要只有兩個 function
adopt 與 getAdopters
所以測試會從這兩大項去做
會先在 test 建立一個測試 sol 檔案 TestAdoption.sol
需要先宣告一個 Contract body 如下
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "truffle/Assert.sol";
import "truffle/DeployedAddresses.sol";
import "../contracts/Adoption.sol";
contract TestAdoption {
Adoption adoption = Adoption(DeployedAddresses.Adoption());
uint expectedPetId = 8;
address expectedAdopter = address(this);
}
Assert.sol 是 truffle 所提供的 Assertion 功能
DeployedAddresses.sol 是 truffle 用來取這個測試 Contract 發佈出去的 address
這邊這個 this 會取得當下的 contract address
因為每次領養人會被設定為 msg.sender 所以如果要做測試需要取的當下呼叫者的 address ,所以這邊用 this
adopt 功能會傳入要領養的寵物 id 並且再設定完領養人後回傳 petId
所以這邊要做的驗證是證明傳入的 petId 與執行完收到的一樣的
邏輯如下
// Testing the adopt() function
function testUserCanAdoptPet() public {
uint returnedId = adoption.adopt(expectedPetId);
Assert.equal(returnedId, expectedPetId, "Adoption of the expected pet should match what is returned.");
}
這邊需要驗證是否能夠取得正確的 Adopters 資料
所以透過前面的 expectedPetId 去測試取的回來的 getAdopters 是否對應到正確的 address
邏輯如下
function testGetAdopterAddressByPetIdInArray() public {
// Store adopters in memory rather than contract's storage
address[16] memory adopters = adoption.getAdopters();
Assert.equal(adopters[expectedPetId], expectedAdopter, "Owner of the expected pet should be this contract");
}
邏輯如下
// Testing retrieval of a single pet's owner
function testGetAdopterAddressByPetId() public {
address adopter = adoption.adopters(expectedPetId);
Assert.equal(adopter, expectedAdopter, "Owner of the expected pet should be this contract");
}
truffle 提供可以使用 mocha 與 chai 的 assert 語法
首先建立 testAdoption.test.js 如下
const Adoption = artifacts.require("Adoption");
contract("Adoption", (accounts) => {
let adoption;
let expectedPetId = 8;
before(async () => {
adoption = await Adoption.deployed();
})
})
這邊的 artifacts.require 可以用來讀取建立好的 Adoption Contract
這裡的 Adoption.deployed() 可以用來取得 deploy 之後的 Contract 實體
然後就可以透過 adoption 來呼叫 Adoption Contract
如同前面所述測試內容撰寫如下
const Adoption = artifacts.require("Adoption");
contract("Adoption", (accounts) => {
let adoption;
let expectedPetId = 8;
before(async () => {
adoption = await Adoption.deployed();
})
describe("adopting a pet and retrieving account addresses", async () => {
before("adopt a pet using accounts[0]", async () => {
await adoption.adopt(expectedPetId, { from: accounts[0] });
expectedAdopter = accounts[0];
});
it("can fetch the address of an owner by pet id", async () => {
const adopter = await adoption.adopters(expectedPetId);
assert.equal(adopter, expectedAdopter, "The owner of the adopted pet should be the first account.");
});
it("can fetch the collection of all pet owners' addresses", async () => {
const adopters = await adoption.getAdopters();
assert.equal(adopters[expectedPetId], expectedAdopter, "The owner of the adopted pet should be in the collection.");
});
});
})
truffle test
主要分為四大部份
function initWeb3() {
if (window.ethereum) {
App.web3Provider = window.ethereum;
try {
// Request account access
await window.ethereum.enable();
} catch (error) {
// User denied account access...
console.error("User denied account access")
}
} // Legacy dapp browsers...
else if (window.web3) {
App.web3Provider = window.web3.currentProvider;
}
// If no injected web3 instance is detected, fall back to Ganache
else {
App.web3Provider = new Web3.providers.HttpProvider('http://localhost:7545');
}
web3 = new Web3(App.web3Provider);
return App.initContract();
}
function initContract() {
$.getJSON('Adoption.json', function(data) {
// Get the necessary contract artifact file and instantiate it with @truffle/contract
var AdoptionArtifact = data;
App.contracts.Adoption = TruffleContract(AdoptionArtifact);
// Set the provider for our contract
App.contracts.Adoption.setProvider(App.web3Provider);
// Use our contract to retrieve and mark the adopted pets
return App.markAdopted();
});
return App.bindEvents();
}
function markAdopted() {
var adoptionInstance;
App.contracts.Adoption.deployed().then(function(instance) {
adoptionInstance = instance;
return adoptionInstance.getAdopters.call();
}).then(function(adopters) {
for (i = 0; i < adopters.length; i++) {
if (adopters[i] !== '0x0000000000000000000000000000000000000000') {
$('.panel-pet').eq(i).find('button').text('Success').attr('disabled', true);
}
}
}).catch(function(err) {
console.log(err.message);
});
}
function handleAdopt(event) {
event.preventDefault();
var petId = parseInt($(event.target).data('id'));
var adoptionInstance;
web3.eth.getAccounts(function(error, accounts) {
if (error) {
console.log(error);
}
var account = accounts[0];
App.contracts.Adoption.deployed().then(function(instance) {
adoptionInstance = instance;
// Execute adopt as a transaction by sending account
return adoptionInstance.adopt(petId, {from: account});
}).then(function(result) {
return App.markAdopted();
}).catch(function(err) {
console.log(err.message);
});
});
}
yarn run dev
以上就是完整的 pet-shop dapp
https://trufflesuite.com/guides/pet-shop/